package br.com.simtecnologia.access.controll.impl;

import java.util.Arrays;

import br.com.caelum.vraptor.Result;
import br.com.caelum.vraptor.core.MethodInfo;
import br.com.caelum.vraptor.ioc.Component;
import br.com.caelum.vraptor.ioc.Container;
import br.com.caelum.vraptor.ioc.SessionScoped;
import br.com.caelum.vraptor.resource.ResourceMethod;
import br.com.simtecnologia.access.controll.DefaultLoginSession;
import br.com.simtecnologia.access.controll.LogicRoute;
import br.com.simtecnologia.access.controll.LoginFlow;
import br.com.simtecnologia.access.controll.LoginSession;
import br.com.simtecnologia.util.ManyToOneMap;
import br.com.simtecnologia.util.Pair;

@Component
@SessionScoped
public class DefaultLoginFlow implements LoginFlow {
	private static final long serialVersionUID = 10412660174604718L;
	private long lastId = 0;
	private ManyToOneMap<Long, Class<? extends LoginSession>, LogicRoute> map = new ManyToOneMap<Long, Class<? extends LoginSession>, LogicRoute>();
	private Pair<Class<? extends LoginSession>, LogicRoute> lastAccessRoute; 
	private transient Container container;
	
	public DefaultLoginFlow() {}
	
	Container getContainer() {
		return container;
	}
	
	void setContainer(Container container) {
		this.container = container;
	}
	
	private Pair<Class<? extends LoginSession>, LogicRoute> createLastAccessRoute(LoginSession loginSession,MethodInfo methodInfo) {
		return createLastAccessRoute(loginSession, methodInfo.getResourceMethod(), methodInfo.getParameters());
	}
	
	private Pair<Class<? extends LoginSession>, LogicRoute> createLastAccessRoute(LoginSession loginSession,ResourceMethod method, Object[] parameters) {
		Class<? extends LoginSession> sessionClass = getCorrectSessionClass(loginSession);
		return new Pair<Class<? extends LoginSession>, LogicRoute>(sessionClass, new DefaultLogicRoute(method,parameters));
	}
	
	synchronized long addLoginRequest(LoginSession loginSession,ResourceMethod method, Object[] parameters) {
		return addLoginRequest(
				createLastAccessRoute(loginSession,method,parameters)
		);
	}

	/**
	 * @deprecated MethodInfo its has not parameters set yet at {@link AccessInterceptor} time.
	 */
	@Deprecated
	synchronized long addLoginRequest(LoginSession loginSession,MethodInfo from) {
		return addLoginRequest(
				createLastAccessRoute(loginSession,from)
		);
	}
	
	private long addLoginRequest(Pair<Class<? extends LoginSession>, LogicRoute> accessRoute) {
		long id = generateConversationId();
		lastAccessRoute = accessRoute;
		map.put(id, lastAccessRoute.getFirst(), lastAccessRoute.getSecond());
		return id;
	}
	
	private synchronized long generateConversationId() {
		long newId = lastId;
		while (newId == lastId) {
			newId = System.currentTimeMillis();
		}
		lastId = newId;
		return newId;
	}
	
	/**
	 * Verifica se há rota de recuperação de fluxo para a <tt>sessionClass</tt> enviada.  
	 * @param sessionClass a classe de login a qual se deseja verificar se há rota.
	 */
	public synchronized boolean hasRetrieveRoute(Class<? extends LoginSession> sessionClass) {
		return map.containsOne(sessionClass);
	}
	/**
	 * Verifica se há rota de recuperação de fluxo para a <tt>conversationId</tt> enviada.  
	 * @param conversationId a identificação de conversation, este ID é usado para recupera
	 * 			a rota especifica de um acesso que foi desviado para página de login.
	 * 			ao ter uma rota desviada para o login, este codigo passa a ser acessivel
	 * 			na view usando-se ${loginflow} e ser acessado na lógica usando-se
	 * 			<br/><tt>result.included().get("loginflow");</tt>
	 * 			
	 */
	public synchronized boolean hasRetrieveRoute(Long conversationId) {
		return map.containsMany(conversationId);
	}
	
	private Result getResult() {
		return getContainer().instanceFor(Result.class);
	}
	
	/**
	 * O mesmo que <tt>retrieveFlowBeforeLogin(DefaultLoginSession.class)</tt>, ou seja
	 * tenta recuperar o fluxo para a sessão padrão de login.
	 * @param result 
	 * @return
	 */
	public synchronized boolean retrieveFlowBeforeLogin() {
		return retrieveFlowBeforeLogin(DefaultLoginSession.class);
	}
	
	public synchronized boolean retrieveFlowBeforeLogin(Long conversationId) {
		return retrieveFlow(getResult(),map.getPairOne(conversationId));
	}
	
	private Class<? extends LoginSession> getCorrectSessionClass(Class<? extends LoginSession> sessionClass) {
		return Arrays.asList(sessionClass.getInterfaces()).contains(DefaultLoginSession.class) ?
				DefaultLoginSession.class : sessionClass;
	}
	
	private Class<? extends LoginSession> getCorrectSessionClass(
			LoginSession loginSession) {
		return DefaultLoginSession.class.isInstance(loginSession) ? 
				DefaultLoginSession.class : loginSession.getClass();
	}
	
	
	public synchronized boolean retrieveFlowBeforeLogin(Class<? extends LoginSession> sessionClass) {
		sessionClass = getCorrectSessionClass(sessionClass);
		return !map.containsOne(sessionClass) ? false : 
				executeRetrive(sessionClass,map.getPairMany(sessionClass).getSecond(), getResult());
	}
	

//	private synchronized boolean retrieveFlowBeforeLogin(Result result) {
//		return retrieveFlowBeforeLogin(result,DefaultLoginSession.class);
//	}
//	
//	private synchronized boolean retrieveFlowBeforeLogin(Result result,Long conversationId) {
//		return retrieveFlow(result,map.getPairOne(conversationId));
//	}
//	
//	private synchronized boolean retrieveFlowBeforeLogin(Result result,Class<? extends LoginSession> sessionClass) {
//		return executeRetrive(sessionClass,map.getPairMany(sessionClass).getSecond(), result);
//	}
	
	private boolean retrieveFlow(Result result,Pair<Class<? extends LoginSession>, LogicRoute> to) {
		if (to != null)
			return executeRetrive(to.getFirst(),to.getSecond(), result);
		return false;
	}
	
	private boolean executeRetrive(Class<? extends LoginSession> sessionClass,LogicRoute logicRoute, Result result) {
		if (sessionClass != null && result != null) {
			LoginSession loginSession = getContainer().instanceFor(sessionClass);
			if (loginSession.isLogged())
				unlock(loginSession);
			logicRoute.redirectTo(result);
			return true;
		}
		return false;
	}

	void unlock(LoginSession loginSession) {
		map.removeOne(getCorrectSessionClass(loginSession));
	}
}
